// Copyright (C) Calum Grant 2008

#define _SCL_SECURE_NO_WARNINGS

#include <cppscript>
#include "unit_tests.hpp"


namespace 
{
	var obj1_compute(var obj1)
	{
		return obj1["a"] + obj1["b"];
	}

	var obj1_create()
	{
		return object().extend
			("a", 12)
			("b", 13)
			("compute", obj1_compute);
	}

	void producer_produce(var producer)
	{
		for(int i=0; i<10; ++i)
		{
			producer["queue"].push_back(i);
			producer["item_available_event"]["signal"]();
		}
	}

	var producer_create()
	{
		return object().extend
			("queue", list())
			("function", producer_produce)
			("item_available_event", event());
	}

	var consumer_pop_item(var consumer)
	{
		// Don't wait every time since one event might signal several items.
		if(consumer["queue"].empty())
			consumer["item_available_event"]["wait"]();

		var item = consumer["queue"].pop_front();
		return item;
	}

	var consumer_consume(var consumer)
	{
		var total=0;
		for(int i=0; i<10; ++i)
		{
			total += consumer["pop_item"]();
		}
		return total;
	}

	var consumer_create(var producer)
	{
		return object("consumer").extend
			("pop_item",consumer_pop_item)
			("queue", producer["queue"])
			("function", consumer_consume)
			("item_available_event", producer["item_available_event"]);
	}

	void summer_receive(var summer, var value)
	{
		summer["total"] +=  value;
	}
}


void test_queue()
{
	var summer = object().extend
		("receive", summer_receive)
		("total", 0);

	var q = message_queue(summer["receive"],5);
	finally( q["close"] );

	foreach(i, range(1,100)) 
		q["post"](i);

	q["wait"]();	// Wait for queue to empty

	assertx( summer["total"] == 5050 );
}


void thread_throws1()
{
	throw var(123);
}


void test_threads()
{
	// Test join
	var obj1 = obj1_create();
	var thread1 = thread(obj1["compute"]);
	var result = thread1["join"]();
	assert( result == 25 );

	// Test throwing across threads
	var thread2 = thread( thread_throws1 );
	try
	{
		thread2["join"]();
		assertx(0);
	}
	catch( var v )
	{
		assertx( v == 123 );
	}

	var producer = producer_create();
	var consumer = consumer_create(producer);
	var consumer_thread = thread(consumer["function"]);
	var producer_thread = thread(producer["function"]);

	producer_thread["join"]();

	var total = consumer_thread["join"]();
	assert( total == 45 );

	// Mutex
	var m = mutex();
	m["lock"]();
	finally(m["unlock"]);
}


namespace
{
	volatile int t1_value=0;
	volatile int t2_value=0;


	void thread_cmd() 
	{ 
		++t1_value; 
	}


	void thread_cmd2(var value) 
	{ 
		t2_value += value.as_int(); 
	}


	var vfn(var numbers)
	{
		var total=0;
		foreach(n, numbers) total += n;
		return total;
	}


	namespace x
	{
		var array() { return dynamic::array(); }
	}
}


void test_proxy()
{
	using internal::proxy;

	// proxy.construction
	var ps = proxy("Fillet steak");

	// proxy.shared
	assert( !ps.impl().shared_var() );

	// proxy.class_name
	assert( ps.class_name() == "string" );

	// proxy.compare
	assert( ps.impl().comparison_index() == cmp_proxy );
	assert( ps > 123 );
	assert( ps == ps );
	assert( ps != +ps );
	assert( var(2).proxy() == 2 );
	assert( proxy(2) != 2 );

	// proxy.clone
	var ps2 = +ps;
	assert( ps2.as_string() == "Fillet steak" );
	ps2.clear();
	assert( ps2.as_string() == "" );
	assert( ps.as_string() == "Fillet steak" );

	// proxy.as_int
	assert( proxy("123").as_int() == 123 );
	assert( proxy("123").as_string() == "123" );
	assert( proxy("123").as_wstring() == L"123" );
	assert( proxy("12.5").as_double() == 12.5 );
	assert( proxy("1") );
	assert( ps2.impl().as_ptr()==0 );

	// proxy.proxy
	assert( ps2.proxy() == ps2 );

	// proxy.as_nonroot
    assert( var(object().extend("a", ps))["a"].as_string() == "Fillet steak");

	// proxy.call
	var fp = proxy(varargs(vfn));
	assert( fp() == 0 );
	assert( fp(1) == 1 );
	assert( fp(1,2) == 3 );
	assert( fp(1,2,3) == 6 );
	assert( fp(1,2,3,4) == 10 );
	assert( fp(1,2,3,4,5) == 15 );
	assert( fp(1,2,3,4,5,6) == 21 );
	assert( fp(1,2,3,4,5,6,7) == 28 );
	assert( fp(1,2,3,4,5,6,7,8) == 36 );
	assert( fp(1,2,3,4,5,6,7,8,9) == 45 );
	assert( fp(1,2,3,4,5,6,7,8,9,10) == 55 );
	assert( proxy(vfn).max_args() == 1 );

	// proxy.member_call
	var op = proxy(object().extend("m", varargs(vfn)));
	assert( op["m"]() == 0 );
	assert( op["m"](1) == 1 );
	assert( op["m"](1,2) == 3 );
	assert( op["m"](1,2,3) == 6 );
	assert( op["m"](1,2,3,4) == 10 );
	assert( op["m"](1,2,3,4,5) == 15 );
	assert( op["m"](1,2,3,4,5,6) == 21 );
	assert( op["m"](1,2,3,4,5,6,7) == 28 );
	assert( op["m"](1,2,3,4,5,6,7,8) == 36 );
	assert( op["m"](1,2,3,4,5,6,7,8,9) == 45 );
	assert( op["m"](1,2,3,4,5,6,7,8,9,10) == 55 );

	// proxy.begin, end
	var items2[] = { 1,2,3,4 };
	var pi1 = items2;
	var pi2 = proxy(items2);

	assert( std::equal(pi1.begin(), pi1.end(), pi2.begin() ) );

	// proxy.rbegin, rend
	assert( std::equal(pi1.rbegin(), pi1.rend(), pi2.rbegin() ) );

	// Proxy.enumerator
	var i = pi2.enumerator();
	assert( i );
	assert( *i++ == 1 );
	assert( i );
	assert( *i++ == 2 );
	assert( *i++ == 3 );
	assert( *i++ == 4 );
	assert( !i );

	// proxy.reverse_enumerator
	i = pi2.reverse_enumerator();
	assert( i );
	assert( *i++ == 4 );
	assert( i );
	assert( *i++ == 3 );
	assert( *i++ == 2 );
	assert( *i++ == 1 );
	assert( !i );

	// proxy.size
	var items[] = { 1,2,"3" };
	var pl = proxy(items);
	assert( pl.size() == 3 );
	pl.resize(4);
	assert( pl.size() == 4 );
	assert( pl );
	assert( !pl.empty() );

	// proxy.front
	assert( pl.front() == 1 );

	// proxy.back
	assert( pl.back() == null );

	// proxy.clear
	pl.clear();
	assert( pl.empty() );

	// proxy.keys()
	assert( pi2.keys().size() == 4 );
	assert( pi2.keys()[0] == 0 );
	assert( pi2.values().size() == 4 );
	assert( pi2.values()[0] == 1 );

	// proxy.get_member / set_member
	var a = proxy(map());
	var b = proxy(object());
	var c = proxy(array());
	a[1] = 12;
	assert( a[1] == 12 );
	assert_throws( var, b[12]=3 );
	assert_throws( var, b[var(1)]=1 );
	b["x"] = 13;
	assert( b["x"] == 13 );
	c[1] = 3;
	assert( c[0] == null );

	// proxy.push_back / push_front
	var list_proxy = proxy(list());
	list_proxy.push_back(3);
	list_proxy.push_back(1);
	assert(list_proxy.back() == 1);
	list_proxy.push_front(2);
	assert( list_proxy.front() == 2 );

	// proxy.pop_back / pop_front

	assert( list_proxy.pop_front() == 2 );
	assert( list_proxy.pop_back() == 1 );

	// proxy.contains
	var po = proxy( object().extend("a", "a" ) );
	assert( po.contains("a") );
	assert( !po.contains("b") );
	assert( !po.contains(2) );

	// proxy.erase
	po.erase("a");
	assert( !po.contains("a") );

	// proxy.member_inc/dec
	po["a"] = 1;
	++po["a"];
	assert( po["a"] == 2 );
	-- po["a"];
	assert( po["a"] == 1 );

	// proxy.member_add/sub
	po["a"] += 3;
	assert( po["a"] == 4 );
	po["a"] -= 1;
	assert( po["a"] == 3 );

	// proxy.mixin
	assert_throws( not_supported, po.mixin( object().extend("b", 8) ) );

	// proxy.inc
	var p = proxy(1);
	++p;
	assert( p.as_int() == 2 );

	// proxy.dec
	--p;
	assert( p.as_int() == 1 );

	// proxy.key
	assert( po.begin().key().as_string() == "a" );
	
	// proxy.value
	assert( po.begin().value() == 3 );

	// proxy.deref
	assert( (*po.begin()).as_string() == "a" );

	// proxy.operators
	assert( (proxy("abc") + "def").as_string() == "abcdef" );
	assert_throws( var, proxy(x::array)+1 );

	assert( (proxy(4) - proxy(3)).as_int() == 1 );
	assert_throws( var, proxy(x::array)-1 );

	assert( (proxy(4) * proxy(3)).as_int() == 12 );
	assert_throws( var, proxy(x::array)*1 );

	assert( (proxy(6) / proxy(3)).as_int() == 2 );
	assert_throws( var, proxy(x::array)/1 );

	assert( (proxy(6) % proxy(3)).as_int() == 0 );
	assert_throws( var, proxy(x::array)%1 );

	assert( (+proxy(2)).as_int() == 2 );
	assert( (-proxy(2)).as_int() == -2 );

	assert( (proxy(3)<<1).as_int() == 6 );
	assert( (proxy(12)>>2).as_int() == 3 );
	assert( (proxy(3)&2).as_int() == 2);
	assert( (proxy(1)|2).as_int() == 3);
	assert( (~proxy(~10)).as_int() == 10);
	assert( (proxy(1)^5).as_int() == 4);

	// proxy.assignment
	var pa = proxy(1);
	assert( (pa += 2).as_int() == 3 );
	assert( (pa -= 1).as_int() == 2 );
	assert( (pa *= 4).as_int() == 8 );
	assert( (pa /= 2).as_int() == 4 );
	assert( (pa %= 3).as_int() == 1 );
	assert( (pa <<= 3).as_int() == 8 );
	assert( (pa >>= 1).as_int() == 4 );
	assert( (pa |= 2).as_int() == 6 );
	assert( (pa &= 2).as_int() == 2 );
	assert( (pa ^= 6).as_int() == 4 );

	// So as not to upset other tests
	gc_force_collect();
}
